<!DOCTYPE html>
<html>
<head>
<script src="Node.js"></script>
<script src="Type.js"></script>
<script src="ReferenceType.js"></script>
<script src="Value.js"></script>
<script src="Expression.js"></script>
<script src="Rewriter.js"></script>
<script src="Visitor.js"></script>
<script src="CreateLiteral.js"></script>
<script src="CreateLiteralType.js"></script>
<script src="PropertyAccessExpression.js"></script>

<script src="AddressSpace.js"></script>
<script src="AnonymousVariable.js"></script>
<script src="ArrayRefType.js"></script>
<script src="ArrayType.js"></script>
<script src="Assignment.js"></script>
<script src="AutoWrapper.js"></script>
<script src="Block.js"></script>
<script src="BoolLiteral.js"></script>
<script src="Break.js"></script>
<script src="CallExpression.js"></script>
<script src="CallFunction.js"></script>
<script src="Check.js"></script>
<script src="CheckLiteralTypes.js"></script>
<script src="CheckLoops.js"></script>
<script src="CheckRecursion.js"></script>
<script src="CheckRecursiveTypes.js"></script>
<script src="CheckReturns.js"></script>
<script src="CheckUnreachableCode.js"></script>
<script src="CheckWrapped.js"></script>
<script src="Checker.js"></script>
<script src="CloneProgram.js"></script>
<script src="CommaExpression.js"></script>
<script src="ConstexprFolder.js"></script>
<script src="ConstexprTypeParameter.js"></script>
<script src="Continue.js"></script>
<script src="ConvertPtrToArrayRefExpression.js"></script>
<script src="DoWhileLoop.js"></script>
<script src="DotExpression.js"></script>
<script src="DoubleLiteral.js"></script>
<script src="DoubleLiteralType.js"></script>
<script src="DereferenceExpression.js"></script>
<script src="EArrayRef.js"></script>
<script src="EBuffer.js"></script>
<script src="EBufferBuilder.js"></script>
<script src="EPtr.js"></script>
<script src="EnumLiteral.js"></script>
<script src="EnumMember.js"></script>
<script src="EnumType.js"></script>
<script src="EvaluationCommon.js"></script>
<script src="Evaluator.js"></script>
<script src="ExpressionFinder.js"></script>
<script src="ExternalOrigin.js"></script>
<script src="Field.js"></script>
<script src="FindHighZombies.js"></script>
<script src="FlattenProtocolExtends.js"></script>
<script src="FlattenedStructOffsetGatherer.js"></script>
<script src="FloatLiteral.js"></script>
<script src="FloatLiteralType.js"></script>
<script src="FoldConstexprs.js"></script>
<script src="ForLoop.js"></script>
<script src="Func.js"></script>
<script src="FuncDef.js"></script>
<script src="FuncInstantiator.js"></script>
<script src="FuncParameter.js"></script>
<script src="FunctionLikeBlock.js"></script>
<script src="HighZombieFinder.js"></script>
<script src="IdentityExpression.js"></script>
<script src="IfStatement.js"></script>
<script src="IndexExpression.js"></script>
<script src="InferTypesForCall.js"></script>
<script src="Inline.js"></script>
<script src="Inliner.js"></script>
<script src="InstantiateImmediates.js"></script>
<script src="IntLiteral.js"></script>
<script src="IntLiteralType.js"></script>
<script src="Intrinsics.js"></script>
<script src="LateChecker.js"></script>
<script src="Lexer.js"></script>
<script src="LexerToken.js"></script>
<script src="LiteralTypeChecker.js"></script>
<script src="LogicalExpression.js"></script>
<script src="LogicalNot.js"></script>
<script src="LoopChecker.js"></script>
<script src="MakeArrayRefExpression.js"></script>
<script src="MakePtrExpression.js"></script>
<script src="NameContext.js"></script>
<script src="NameFinder.js"></script>
<script src="NameResolver.js"></script>
<script src="NativeFunc.js"></script>
<script src="NativeFuncInstance.js"></script>
<script src="NativeType.js"></script>
<script src="NativeTypeInstance.js"></script>
<script src="NormalUsePropertyResolver.js"></script>
<script src="NullLiteral.js"></script>
<script src="NullType.js"></script>
<script src="OriginKind.js"></script>
<script src="OverloadResolutionFailure.js"></script>
<script src="Parse.js"></script>
<script src="Prepare.js"></script>
<script src="PropertyResolver.js"></script>
<script src="Program.js"></script>
<script src="ProgramWithUnnecessaryThingsRemoved.js"></script>
<script src="Protocol.js"></script>
<script src="ProtocolDecl.js"></script>
<script src="ProtocolFuncDecl.js"></script>
<script src="ProtocolRef.js"></script>
<script src="PtrType.js"></script>
<script src="ReadModifyWriteExpression.js"></script>
<script src="RecursionChecker.js"></script>
<script src="RecursiveTypeChecker.js"></script>
<script src="ResolveNames.js"></script>
<script src="ResolveOverloadImpl.js"></script>
<script src="ResolveProperties.js"></script>
<script src="ResolveTypeDefs.js"></script>
<script src="Return.js"></script>
<script src="ReturnChecker.js"></script>
<script src="ReturnException.js"></script>
<script src="StandardLibrary.js"></script>
<script src="StatementCloner.js"></script>
<script src="StructLayoutBuilder.js"></script>
<script src="StructType.js"></script>
<script src="Substitution.js"></script>
<script src="SwitchCase.js"></script>
<script src="SwitchStatement.js"></script>
<script src="SynthesizeEnumFunctions.js"></script>
<script src="SynthesizeStructAccessors.js"></script>
<script src="TrapStatement.js"></script>
<script src="TypeDef.js"></script>
<script src="TypeDefResolver.js"></script>
<script src="TypeOrVariableRef.js"></script>
<script src="TypeParameterRewriter.js"></script>
<script src="TypeRef.js"></script>
<script src="TypeVariable.js"></script>
<script src="TypeVariableTracker.js"></script>
<script src="TypedValue.js"></script>
<script src="UintLiteral.js"></script>
<script src="UintLiteralType.js"></script>
<script src="UnificationContext.js"></script>
<script src="UnreachableCodeChecker.js"></script>
<script src="VariableDecl.js"></script>
<script src="VariableRef.js"></script>
<script src="VisitingSet.js"></script>
<script src="WSyntaxError.js"></script>
<script src="WTrapError.js"></script>
<script src="WTypeError.js"></script>
<script src="WhileLoop.js"></script>
<script src="WrapChecker.js"></script>
<style>
#ShaderSource {
    font-family: monospace;
    width: 700px;
    height: 400px;
}

td {
    border: 1px solid black;
}
</style>
<script>
let defaultShaderSource = `struct VertexInput {
    float2 position;
    float3 color;
}

struct VertexOutput {
    float4 wsl_Position;
    float3 color;
}

struct FragmentOutput {
    float4 wsl_Color;
}

vertex VertexOutput vertexShader(VertexInput vertexInput) {
    VertexOutput result;
    result.wsl_Position = float4(vertexInput.position, 0., 1.);
    result.color = vertexInput.color;
    return result;
}

fragment FragmentOutput fragmentShader(VertexOutput stageIn) {
    FragmentOutput result;
    result.wsl_Color = float4(stageIn.color, 1.);
    return result;
}`;

let shaderSourceTextarea;
let compileButton;
let localStorage;
let compileLogElement;
let vertexShaderSelect;
let fragmentShaderSelect;
let dataTable;
let resultTable;

let program;
let availableVertexShaders;
let availableFragmentShaders;
let currentVertexShader;
let currentFragmentShader;
let inlinedVertexShader;
let inlinedFragmentShader;
let allArgumentInfos;
let argumentEPtrs;
let stageInArgumentIndex;

window.addEventListener("load", function() {
    shaderSourceTextarea = document.getElementById("ShaderSource");
    compileButton = document.getElementById("CompileButton");
    compileButton.addEventListener("click", compilePresentShaderSource);
    compileLogElement = document.getElementById("CompileLog");
    vertexShaderSelect = document.getElementById("VertexShaderSelect");
    fragmentShaderSelect = document.getElementById("FragmentShaderSelect");
    vertexShaderSelect.addEventListener("change", selectedShadersChanged);
    fragmentShaderSelect.addEventListener("change", selectedShadersChanged);
    dataTable = document.getElementById("DataTable");
    document.getElementById("RunButton").addEventListener("click", runShaders);
    resultTable = document.getElementById("ResultTable");
    shaderSourceTextarea.addEventListener("input", sourceCodeChanged);

    let shaderSource;
    try {
        localStorage = window.localStorage;
        shaderSource = localStorage.getItem("ShaderSource");
    } catch (e) {
    }

    if (!shaderSource)
        shaderSource = defaultShaderSource;
    shaderSourceTextarea.value = shaderSource;
    window.setTimeout(compilePresentShaderSource, 0);
});

function clearDataTable()
{
    while (dataTable.lastChild instanceof HTMLTableRowElement)
        dataTable.removeChild(dataTable.lastChild);
}

function clearResultTable()
{
    while (resultTable.lastChild instanceof HTMLTableRowElement)
        resultTable.removeChild(resultTable.lastChild);
}

function clearShaderSelect()
{
    while (vertexShaderSelect.firstChild)
        vertexShaderSelect.removeChild(vertexShaderSelect.firstChild);
    while (fragmentShaderSelect.firstChild)
        fragmentShaderSelect.removeChild(fragmentShaderSelect.firstChild);
}

function sourceCodeChanged() {
    compileLogElement.textContent = "";
    clearShaderSelect();
    clearDataTable();
    clearResultTable();
}

function presentError(message) {
    compileLogElement.textContent = message;
    clearShaderSelect();
    clearDataTable();
    clearResultTable();
    throw new Error(message);
}

function compilePresentShaderSource() {
    compileLogElement.textContent = "Compiling...";
    compileButton.disabled = true;
    window.setTimeout(doCompilePresentShaderSource, 0);
}

function doCompilePresentShaderSource() {
    compileButton.disabled = false;
    let start = new Date().getTime();
    let result;
    try {
        result = prepare("/internal/test", 0, shaderSourceTextarea.value);
    } catch (e) {
        result = e;
    }
    let end = new Date().getTime();
    if (!(result instanceof Program)) {
        compileLogElement.textContent = result.toString();
        return;
    } else
        compileLogElement.textContent = "Compile successful!";

    try {
        shaderSource = localStorage.setItem("ShaderSource", shaderSourceTextarea.value);
    } catch (e) {
    }

    program = result;
    availableVertexShaders = [];
    availableFragmentShaders = [];
    for (let functionNames of program.functions.values()) {
        for (let func of functionNames) {
            if (func.shaderType == "vertex")
                availableVertexShaders.push(func);
            if (func.shaderType == "fragment")
                availableFragmentShaders.push(func);
        }
    }
    clearShaderSelect();
    function createOption(textContent) {
        let option = document.createElement("option");
        option.textContent = textContent;
        return option;
    }
    for (let i = 0; i < availableVertexShaders.length; ++i)
        vertexShaderSelect.appendChild(createOption(availableVertexShaders[i].name));
    for (let i = 0; i < availableFragmentShaders.length; ++i)
        fragmentShaderSelect.appendChild(createOption(availableFragmentShaders[i].name));
    selectedShadersChanged(true);
}

function createTableRow(name, elementCallback) {
    let row = document.createElement("tr");
    let variableCell = document.createElement("td");
    let valueCell = document.createElement("td");
    variableCell.textContent = name;
    valueCell.appendChild(elementCallback());
    row.appendChild(variableCell);
    row.appendChild(valueCell);
    return row;
    dataTable.appendChild(row);
}

function linkError(message) {
    compileLogElement.textContent = message + " Fix this by selecting a different pair of shaders below.";
    clearDataTable();
    clearResultTable();
}

function selectedShadersChanged(fromCompilation = false) {
    if (!fromCompilation)
        compileLogElement.textContent = "";
    currentVertexShader = availableVertexShaders[vertexShaderSelect.selectedIndex];
    currentFragmentShader = availableFragmentShaders[fragmentShaderSelect.selectedIndex];

    inlinedVertexShader = program.funcInstantiator.getUnique(currentVertexShader, []);
    _inlineFunction(program, inlinedVertexShader, new VisitingSet(currentVertexShader));

    allArgumentInfos = [];
    argumentEPtrs = [];
    for (let parameter of inlinedVertexShader.parameters) {
        let ePtr = new EPtr(new EBuffer(parameter.type.size), 0);
        argumentEPtrs.push({ePtr: ePtr, size: parameter.type.size});
        let gatherer = new FlattenedStructOffsetGatherer(parameter.name);
        parameter.visit(gatherer);
        for (let argumentInfo of gatherer.result) {
            argumentInfo.ePtr = ePtr;
            allArgumentInfos.push(argumentInfo);
        }
    }
    clearDataTable();
    clearResultTable();
    for (let info of allArgumentInfos) {
        dataTable.appendChild(createTableRow(info.name, function() {
             let input = document.createElement("input");
             input.addEventListener("input", clearResultTable);
             input.type = "text";
             return input;
        }));
    }

    let found = false;
    let index = 0;
    for (let parameter of currentFragmentShader.parameters) {
        if (parameter.name == "stageIn") {
            found = true;
            if (!parameter.type.equals(currentVertexShader.returnType))
                linkError("Vertex shader's return type needs to match the stageIn parameter of the fragment shader!");
            stageInArgumentIndex = index;
            break;
        }
        ++index;
    }
    if (!found)
        linkError("Could not find the stageIn argument in the fragment shader!");
    if (func.parameters.length > 1)
        linkError("Fragment shaders currently don't know how to have any other arguments other than stageIn.");
}

function runShaders() {
    let func = inlinedVertexShader;

    let index = 0;
    for (let i = 0; i < dataTable.children.length; ++i) {
        let childElement = dataTable.children[i];
        if (!(childElement instanceof HTMLTableRowElement))
            continue;
        let value = childElement.children[1].firstChild.value;
        let argumentInfo = allArgumentInfos[index];
        let type = argumentInfo.type;
        switch (type) {
        case "float32":
            value = Math.fround(parseFloat(value));
            argumentInfo.ePtr.set(argumentInfo.offset, value);
            break;
        case "float64":
            value = parseFloat(value);
            break;
        case "int32":
            value = parseInt(value) | 0;
            break;
        case "uint32":
            value = parseInt(value) >>> 0;
            break;
        default:
            presentError("I don't know how to parse values of type " + type);
        }
        if (isNaN(value))
            presentError("Could not parse input data " + index);
        argumentInfo.ePtr.set(argumentInfo.offset, value);
        ++index;
    }

    for (let i = 0; i < func.parameters.length; ++i)
        func.parameters[i].ePtr.copyFrom(argumentEPtrs[i].ePtr, argumentEPtrs[i].size);
    let result = new Evaluator(program).runFunc(func);

    inlinedFragmentShader = program.funcInstantiator.getUnique(currentFragmentShader, []);
    _inlineFunction(program, inlinedFragmentShader, new VisitingSet(currentFragmentShader));
    func = inlinedFragmentShader;

    func.parameters[stageInArgumentIndex].ePtr.copyFrom(result, func.parameters[stageInArgumentIndex].type.size);

    result = new Evaluator(program).runFunc(func);

    clearResultTable();
    let gatherer = new FlattenedStructOffsetGatherer(func.returnType.type.name);
    func.returnType.type.visit(gatherer);
    for (let info of gatherer.result) {
        resultTable.appendChild(createTableRow(info.name, function() {
            return document.createTextNode(result.get(info.offset));
        }));
    }
}
</script>
</head>
<body>
<h1>WebGPU Shading Language</h1>
<ol>
<li>
    Write a shader!
    <div><textarea id="ShaderSource"></textarea></div>
    <div id="CompileLog"></div>
    <div><input type="button" value="Compile" id="CompileButton"></div>
</li>
<li>
    Select your shaders
    <div><label>Vertex Shader <select id="VertexShaderSelect"></select></label></div>
    <div><label>Fragment Shader <select id="FragmentShaderSelect"></select></label></div>
</li>
<li>
    Specify input data
    <table id="DataTable">
        <thead>
            <tr>
                <td>Variable</td>
                <td>Value</td>
            </tr>
        </thead>
    </table>
</li>
<li>
    <div><input type="button" value="Run" id="RunButton"></div>
    <table id="ResultTable">
        <thead>
            <tr>
                <td>Variable</td>
                <td>Value</td>
            </tr>
        </thead>
    </table>
</li>
</ol>
</body>
</html>
